﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Internals;

namespace OpenLS.Spreadsheet
{
    partial class Worksheet
    {
        internal CurrentDeletion CurrentDeletion;

        internal int MaxScrollColumn
        {
            get
            {
                return Math.Max(BottomRight.Column, ScrollPane.Right);

            }
        }

        internal int MaxScrollRow
        {
            get
            {
                return Math.Max(BottomRight.Row, ScrollPane.Bottom);
            }
        }

        private void deleteRows(Row row1, Row row2)
        {
            if (!canMoveRange(new FullRowRange(row1, row2)))
                throw new UnableToModifyRangeException();

            deleteTables(range => range.ContainsRow(row1) && range.ContainsRow(row2));
            fixupSharedFormulas(this, cell => cell.Row >= row1, new CellRelativeAddress(row2.Index - row1.Index - 1, 0));
            using (new TemporaryMovedCells(this, address => address.Row > row2.Index))
            {
                var rowsToRemove = new List<Row>();
                for (int i = row1.Index; i <= row2.Index; i++)
                    rowsToRemove.Add(Rows[i]);
                foreach (Row row in rowsToRemove)
                {
                    foreach (Cell cell in row.GetNonEmptyCells())
                    {
                        removeCellFromColumns(cell);
                        Cells.RemoveNonVolatileCellNoEvents(cell);
                        //\\  Cells.RemoveNonVolatileCell(cell);
                        cell.Delete();
                    }
                    // row.IsDeleted = true;
                }
                OnCellsRemoved(new MultiRowRegion(row1, row2));
                Rows.RemoveDontTouchCells(rowsToRemove);
            }
            raiseLayoutChanged();
        }

        private void deleteTables(Predicate<Range> predicate)
        {
            var toDelete = new List<Table>();
            foreach (var table in Tables)
            {
                if (predicate(table.Range))
                    toDelete.Add(table);
            }
            foreach (var table in toDelete)
            {
                _tables.Remove(table);
            }
        }
        [Obsolete]
        internal class DimensionInsertionEventArgs: EventArgs
        {
            private readonly int _first;
            private readonly int _last;

            public DimensionInsertionEventArgs(int first, int last)
            {
                _first = first;
                _last = last;
            }

            public int First
            {
                get { return this._first; }
            }
        }
        [Obsolete]
        internal class DimensionDeletionEventArgs<T>: EventArgs where T: Dimension
        {
            private readonly T _dim1;
            private readonly T _dim2;

            public DimensionDeletionEventArgs(T dim1, T dim2)
            {
                Contract.Requires(dim1 != null);
                Contract.Requires(dim2 != null);
                Contract.Requires(dim1.Index <= dim2.Index);
                _dim1 = dim1;
                _dim2 = dim2;
            }

            public T   Dimension1
            {
                get { return _dim1; }
            }
        }
        [Obsolete]
        internal event EventHandler<MoveRangeEventArgs> MovingRange;
        private void deleteColumns(Column column1, Column column2)
        {
            if (!canMoveRange(new FullColumnRange(column1, column2)))
                throw new UnableToModifyRangeException();

            deleteTables(range => range.ContainsColumn(column1) && range.ContainsColumn(column2));
            Cells.RemoveVolatileCells(cell => cell.Column >= column1.Index && cell.Column <= column2.Index);
#if DEBUG
            //\\ Cells.IsLocked = true;
#endif
            fixupSharedFormulas(this, cell => cell.Column >= column1,
                                new CellRelativeAddress(0, column1.Index - column2.Index-1));
            using (new TemporaryMovedCells(this, address => address.Column > column2.Index))
            {
                var columnsToRemove = new List<Column>();
                for (int i = column1.Index; i <= column2.Index; i++)
                    columnsToRemove.Add(Columns[i]);
                foreach (Column column in columnsToRemove)
                {
                    foreach (Cell cell in column.GetNonEmptyCells())
                    {
                        removeCellFromRows(cell);
                        Cells.RemoveNonVolatileCellNoEvents(cell);
                        cell.Delete();
                    }
                    //column.IsDeleted = false;
                }
                this.OnCellsRemoved(new MultiColumnRegion(column1, column2));
                Cells.RemoveVolatileCells(cell => cell.Column >= column1.Index && cell.Column <= column2.Index);
                Columns.RemoveDontTouchCells(columnsToRemove);
#if DEBUG
                Cells.IsLocked = false;
#endif
                Columns.NotifyChanges(columnsToRemove);
                raiseLayoutChanged();
            }
        }

        internal void InsertCellsRange(RangeAddress address, CellMoveOrientationHint hint, Range range)
        {
            if (!canMoveRange(this.GetRange(address)))
                throw new UnableToModifyRangeException();

            using (CreateUndoScope(StringTable.InsertRange))
            {
                if (Workbook.UndoEnabled)
                    Workbook.UndoManager.Add(new InsertCellsRangeUndoUnit(this, address, hint));
                using (CreateRangeChangeScope(range, RangeChangeType.Insertion, address, hint))
                {
                    if (hint == CellMoveOrientationHint.Horizontal)
                    {
                        using (var temporaryMovedCells = new TemporaryMovedCells(this,
                                                                                 a =>
                                                                                 a.Column >= address.TopLeft.Column &&
                                                                                 a.Row >= address.TopLeft.Row &&
                                                                                 a.Row <= address.BottomRight.Row))
                        {
                            foreach (Cell cell in temporaryMovedCells.GetCells())
                            {
                                cell.Column = Columns[cell.Column.Index + address.ColumnSpan];
                            }
                        }
                    }
                    else
                    {
                        using (var temporaryMovedCells = new TemporaryMovedCells(this,
                                                                                 a =>
                                                                                 a.Row >= address.TopLeft.Row &&
                                                                                 a.Column >= address.TopLeft.Column &&
                                                                                 a.Column <= address.BottomRight.Column)
                            )
                        {

                            foreach (Cell cell in temporaryMovedCells.GetCells())
                            {
                                cell.Row = Rows[cell.Row.Index + address.RowSpan];
                            }
                        }
                    }
                    raiseLayoutChanged();
                }
                //OnRangeChanged(range, RangeChangeType.Insertion, address, hint);
            }
        }

        internal void InsertRowRange(int firstIndex, int lastIndex)
        {
            if (!canMoveRange(new FullRowRange(Rows[firstIndex])))
                throw new UnableToModifyRangeException();
            lock (Cells.VolatileCellLock)
            {
                using (CreateUndoScope(StringTable.InsertRow))
                {
                    using (CreateRangeChangeScope(null, RangeChangeType.Insertion, new RangeAddress(new CellAddress(firstIndex, 0), new CellAddress(lastIndex, Worksheet.MaxColumnIndex)), CellMoveOrientationHint.Vertical))
                    {
                        using (new StableSelectionScope(this))
                        {
                            fixupSharedFormulas(this, cell => cell.Row.Index >= firstIndex,
                                                new CellRelativeAddress(lastIndex - firstIndex + 1, 0));

                            using (new TemporaryMovedCells(this, a => a.Row >= firstIndex))
                            {
                                for (int i = firstIndex; i <= lastIndex; i++)
                                    Rows.Insert(i);
                                Rows.ExtendFormattingRecords(firstIndex, lastIndex);
                            }
                        }
                        Rows.TruncateOffsets(firstIndex, null);
                        raiseLayoutChanged();
                    }
                    //OnRangeChanged(null, RangeChangeType.Insertion, new RangeAddress(new CellAddress(firstIndex, 0), new CellAddress(lastIndex, Worksheet.MaxColumnIndex)), CellMoveOrientationHint.Vertical);
                
                }
            }
        }

        internal void InsertColumnRange(int firstIndex, int lastIndex)
        {
            if (!canMoveRange(new FullColumnRange(Columns[firstIndex])))
                throw new UnableToModifyRangeException();
            lock (Cells.VolatileCellLock)
            {
                using (CreateUndoScope(StringTable.InsertColumn))
                {
                    using (CreateRangeChangeScope(null, RangeChangeType.Insertion, new RangeAddress(new CellAddress(0, firstIndex), new CellAddress(Worksheet.MaxRowIndex, lastIndex)), CellMoveOrientationHint.Horizontal))
                    {
                        using (new StableSelectionScope(this))
                        {
                            fixupSharedFormulas(this, cell => cell.Column.Index >= firstIndex,
                                                new CellRelativeAddress(0, lastIndex - firstIndex + 1));

                            using (new TemporaryMovedCells(this, a => a.Column >= firstIndex))
                            {
                                for (int i = firstIndex; i <= lastIndex; i++)
                                    Columns.Insert(i);
                                Columns.ExtendFormattingRecords(firstIndex, lastIndex);

                            }
                        }
                        Columns.TruncateOffsets(firstIndex, null);
                        raiseLayoutChanged();
                    }
                    //OnRangeChanged(null, RangeChangeType.Insertion, new RangeAddress(new CellAddress(0, firstIndex), new CellAddress(Worksheet.MaxRowIndex, lastIndex) ), CellMoveOrientationHint.Horizontal);
                }
            }
        }


        public void InsertRange(Range range)
        {
            InsertRangeImp(range, null);
        }
        /// <summary>
        /// Insert a new range relative to the passed range. 
        /// </summary>
        /// <param name="range">The range relative to which the new range will be inserted. If range is a full row range, the new range will be inserted above it. if range is a full column range, the new range will be inserted to the left of it. If range is a cells range, the hint will be used</param>
        /// <param name="hint"></param>
        public void InsertRange(Range range, CellMoveOrientationHint hint)
        {
            if (range == null)
                throw new ArgumentNullException("range");
            InsertRangeImp(range, hint);
        }
        void InsertRangeImp(Range range, CellMoveOrientationHint? hint)
        {
            Debug.WriteLine("InsertRange " + range.Label);

            if (range is CompositeRange)
            {
                CompositeRange compositeRange = (CompositeRange)range;
                foreach (var r in compositeRange.Ranges)
                {
                    InsertRangeImp(range, hint);
                }
                return;
            }
            if (range is FullRowRange)
            {
                FullRowRange fullRowRange = (FullRowRange)range;
                var address = fullRowRange.Address;
                InsertRowRange(address);
                return;
            }
            if (range is FullColumnRange)
            {
                FullColumnRange fullRowRange = (FullColumnRange)range;
                var address = fullRowRange.Address;
                InsertColumnRange(address);
                return;
            }
            CellsRange cellsRange = (CellsRange)range;
            if (hint == null)
                throw new InvalidOperationException("hint is missing");
            InsertCellsRange(cellsRange.Address, hint.Value, cellsRange);
        }

        internal void InsertRowRange(RangeAddress rangeAddress)
        {
            InsertRowRange(rangeAddress.TopLeft.Row, rangeAddress.BottomRight.Row);
        }

        internal void InsertColumnRange(RangeAddress rangeAddress)
        {
            InsertColumnRange(rangeAddress.TopLeft.Column, rangeAddress.BottomRight.Column);
        }

        internal void DeleteFullRange(FullRange range)
        {
            if (!canMoveRange(range))
                throw new UnableToModifyRangeException();

            if (range is FullColumnRange)
            {
                var fullColumnRange = (FullColumnRange)range;

                using (CreateUndoScope(StringTable.DeleteRange))
                {
                    using (new StableSelectionScope(this))
                    {
                        var address = fullColumnRange.Address;
                        using (CreateRangeChangeScope(range, RangeChangeType.Deletion, address, CellMoveOrientationHint.Horizontal))
                        {
                            deleteColumns(fullColumnRange.FirstColumn, fullColumnRange.LastColumn);
                            InvokeLayoutChanged(new DimensionEventArgs(null)); //
                        }//OnRangeChanged(range, RangeChangeType.Deletion, address, CellMoveOrientationHint.Horizontal);
                    }
                }
                return;
            }
            if (range is FullRowRange)
            {
                var fullRowRange = (FullRowRange)range;

                using (CreateUndoScope(StringTable.DeleteRange))
                {
                    using (new StableSelectionScope(this))
                    {
                        var address = fullRowRange.Address;
                        using (CreateRangeChangeScope(range, RangeChangeType.Deletion, address, CellMoveOrientationHint.Vertical))
                        {
                            deleteRows(fullRowRange.FirstRow, fullRowRange.LastRow);
                            InvokeLayoutChanged(new DimensionEventArgs (null)); //
                        } 
                        //OnRangeChanged(range, RangeChangeType.Deletion, address, CellMoveOrientationHint.Vertical);
                    }
                }
                return;
            }
        }

        public void DeleteRange(Range range)
        {
                DeleteRangeImp(range, null);
        }

        /// <summary>
        /// Delete a range from a worksheet. The range must belong to the worksheet.
        /// </summary>
        /// <param name="range">The range to delete</param>
        /// <param name="hint">Specifies how to move the cells. This is used for cells ranges and ignored for full row ranges and full column ranges</param>
        public void DeleteRange(Range range, CellMoveOrientationHint hint)
        {
            DeleteRangeImp(range, hint);
        }
        void DeleteRangeImp(Range range, CellMoveOrientationHint? hint)
        {

            Debug.WriteLine("DeleteRange " + range.Label);

            if (!canMoveRange(range))
                throw new UnableToModifyRangeException();

            using (this.CreateCellValueChangedDisabler())
            {
                using (new StableSelectionScope(this))
                {
                    using (CreateUndoScope(StringTable.DeleteRange))
                    {
                        if (range is FullRange)
                        {
                            DeleteFullRange((FullRange) range);
                            return;
                        }
                        if (range is CompositeRange)
                        {
                            CompositeRange cr = (CompositeRange) range;
                            foreach (var r in cr.Ranges)
                                DeleteRangeImp(r, hint);
                            return;

                        }
                        if (range is FullWorksheetRange)
                        {
                            DeleteFullRange(new FullColumnRange(Columns[0], Columns[MaxColumnIndex]));
                            return;

                        }
                        if (hint == null)
                            throw new NotSupportedException("hint");
                        DeleteCellsRange(range, hint.Value);
                    }
                }
            }
        }

        private void DeleteCellsRange(Range range, CellMoveOrientationHint hint)
        {
            using (Workbook.CreateUndoScope(StringTable.DeleteRange))
            {
                var address = range.Address;
                var cellsRange = (CellsRange) range;
                RangeAddress originalAddress = cellsRange.Address;
                //\\ removeCellsInRange(cr, true);
                PostMoveAction affectedNodes = null;
                using (this.Workbook.CreateCalculationDisabler())
                {
                    using (CreateRangeChangeScope(range, RangeChangeType.Deletion, address, hint))
                    {
                        if (hint == CellMoveOrientationHint.Horizontal)
                        {
                            int columnSpanToMove = Worksheet.MaxColumnIndex - cellsRange.MaxColumnIndex;
                            if (columnSpanToMove > 0)
                            {
                                CellsRange sourceRange = CellsRange.Create(this,
                                                                           new RangeAddress
                                                                               (cellsRange.Address.TopLeft +
                                                                                new CellRelativeAddress(0,
                                                                                                        cellsRange.
                                                                                                            ColumnSpan),
                                                                                new CellAddress(
                                                                                    cellsRange.Address.BottomRight.Row,
                                                                                    Worksheet.MaxColumnIndex)));
                                this.CurrentDeletion = new CurrentDeletion(range.Address, hint);
                                affectedNodes = MoveRange_(sourceRange, originalAddress.TopLeft);
                                CurrentDeletion = null;
                            }
                            else
                            {
                                removeCellsInRange(cellsRange, null, true);
                            }
                        }
                        else
                        {
                            int rowSpanToMove = Worksheet.MaxRowIndex - cellsRange.MaxRowIndex;
                            if (rowSpanToMove > 0)
                            {
                                CellsRange sourceRange = CellsRange.Create(this,
                                                                           new RangeAddress
                                                                               (cellsRange.Address.TopLeft +
                                                                                new CellRelativeAddress(cellsRange.RowSpan,
                                                                                                        0),
                                                                                new CellAddress(Worksheet.MaxRowIndex,
                                                                                                cellsRange.Address.
                                                                                                    BottomRight.
                                                                                                    Column)));
                                this.CurrentDeletion = new CurrentDeletion(range.Address, hint);

                                affectedNodes = MoveRange_(sourceRange, originalAddress.TopLeft);
                                CurrentDeletion = null;
                            }
                            else
                            {
                                removeCellsInRange(cellsRange, null, true);
                            }
                        }
                    }
                }//OnRangeChanged(range, RangeChangeType.Deletion, address, hint);
                if (affectedNodes != null)
                    affectedNodes.Do();
            }
        }
    }
}